﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data;

namespace DataMappers
{
    public abstract class AbstractDataMapper<TKey, TEntity> : IEntityMapper<TKey, TEntity>
                                                                where TEntity : IDomainObject<TKey, TEntity>
    {

        private readonly HashSet<TEntity> _newObjects = new HashSet<TEntity>();
        private readonly HashSet<TEntity> _cleanObjects = new HashSet<TEntity>();
        private readonly HashSet<TEntity> _dirtyObjects = new HashSet<TEntity>();
        private readonly HashSet<TEntity> _removedObjects = new HashSet<TEntity>();

        public ISet<TEntity> NewSet() { return _newObjects; }
        public ISet<TEntity> CleanSet() { return _cleanObjects; }
        public ISet<TEntity> DirtySet() { return _dirtyObjects; }
        public ISet<TEntity> RemovedSet() { return _removedObjects;}

        protected Connection _conn;
        
        
        
        private Dictionary<TKey, TEntity> _identityMap;
        public AbstractDataMapper() {
            _identityMap = new Dictionary<TKey, TEntity>();
            
        }
        public AbstractDataMapper(Connection conn)
        {
            _conn = conn;
            _identityMap = new Dictionary<TKey, TEntity>();
        }

        public void SetConnector(Connection conn){
            _conn = conn;
        }
        protected abstract TKey GetKeyFrom(SqlDataReader sdr);
        protected abstract String DoGetAllStatement();
        protected abstract String DoGetFindStatement();
        protected abstract void BindFindStatement(SqlCommand cmd, TKey key);


        protected abstract TEntity DoLoad(SqlDataReader sdr);

        protected abstract string GetUpdateCommand();
        protected abstract void DoBindUpdate(SqlCommand cmd, TEntity t);

        protected abstract string GetInsertCommand();
        protected abstract void DoBindInsert(SqlCommand cmd, TEntity t);

        protected abstract string GetRemoveCommand();
        protected abstract void DoBindRemove(SqlCommand cmd, TEntity t);

        // ---- METHODS TO EXECUTE AFTER DB ACTION --- //
        protected abstract void FinishUpdateObject(TEntity t);
        protected abstract void FinishNewObject(TEntity t, object id);
        protected abstract void FinishDeleteObject(TEntity t);



        public TEntity Load(SqlDataReader sdr)
        {
            TKey key = GetKeyFrom(sdr);
            TEntity ret = default(TEntity);
            try
            {
                _identityMap.TryGetValue(key, out ret) ;
            }
            catch (Exception) {
                return default(TEntity);
            }
            
            if (ret != null) { return ret; }
            ret = DoLoad(sdr);
            _identityMap.Add(key, ret);
            return ret;
        }

        #region IEntityMapper<TKey,TEntity> Members



        public TEntity GetByID(TKey t)
        {
            TEntity ret = default(TEntity);
            if (_identityMap.TryGetValue(t, out ret)) return ret;            
            try
            {
                _conn.OpenConnection();
                SqlCommand cmd = new SqlCommand(DoGetFindStatement(), _conn.getConnection());
                BindFindStatement(cmd, t);
                SqlDataReader sdr = _conn.ExecuteReader(cmd);
                if (!sdr.Read()) { return default(TEntity); }
                return Load(sdr);
            }
            finally
            {
                _conn.CloseConnection();
            }
        }

        public void Insert(TEntity elem)
        {

            try
            {
                _conn.OpenConnection();
                string strCmd = GetInsertCommand();
                SqlCommand cmd = new SqlCommand(strCmd, _conn.getConnection());
                DoBindInsert(cmd, elem);
                object ret = _conn.ExecuteScalar(cmd);
                FinishNewObject(elem, ret);
                elem.Saved();
            }
            finally
            {
                _conn.CloseConnection();
            }

        }

        public void Update(TEntity elem)
        {

            try
            {
                _conn.OpenConnection();
                string strCmd = GetUpdateCommand();
                SqlCommand cmd = new SqlCommand(strCmd, _conn.getConnection());
                DoBindUpdate(cmd, elem);
                _conn.ExecuteNonQuery(cmd);
                FinishUpdateObject(elem);
                elem.Saved();
            }
            finally
            {

                _conn.CloseConnection();
            }
        }

        public void Delete(TEntity elem)
        {
            try
            {
                _conn.OpenConnection();
                string strCmd = GetRemoveCommand();
                SqlCommand cmd = new SqlCommand(strCmd, _conn.getConnection());
                DoBindRemove(cmd, elem);
                _conn.ExecuteNonQuery(cmd);
                FinishDeleteObject(elem);
                elem.Saved();
            }
            finally
            {
                _conn.CloseConnection();
            }
        }

        public void LoadAllInto(ICollection<TEntity> col)
        {
            try
            {
                _conn.OpenConnection();
                SqlCommand cmd = new SqlCommand(DoGetAllStatement(), _conn.getConnection());
                //fazer load usando identity map

                SqlDataReader sdr = _conn.ExecuteReader(cmd);
                while (sdr.Read())
                {
                    col.Add(Load(sdr));
                }

            }
            finally
            {
                _conn.CloseConnection();
            }
        }

        public void LoadFilteredInto(ICollection<TEntity> col, IFilter filter)
        {
            try
            {
                _conn.OpenConnection();
                SqlCommand cmd = new SqlCommand(DoGetAllStatement() + filter.GetWhereClause(), _conn.getConnection());
                //fazer load usando identity map
                filter.BindValues(cmd);
                SqlDataReader sdr = _conn.ExecuteReader(cmd);
                while (sdr.Read())
                {
                    col.Add(Load(sdr));
                }
                sdr.Close();
            }
            finally
            {
                _conn.CloseConnection();
            }
        }

        public void InsertNewObjects(){
		    foreach(TEntity t in _newObjects.ToList<TEntity>()){
			    Insert(t);
		    }
	    }
	    
	    public void DeleteRemovedObjects(){
		    foreach(TEntity t in _removedObjects.ToList<TEntity>()){
			    Delete(t);
		    }
	    }
		
	    public void UpdateDirtyObjects(){
            foreach (TEntity t in _dirtyObjects.ToList<TEntity>())
            {
			    Update(t);
		    }
	    }

        public void Refresh()
        {
            _identityMap.Clear();
        }

        public void DeleteAllObjects()
        {
            List<TEntity> ents = new List<TEntity>();
            LoadAllInto(ents);
            foreach (var item in ents)
            {
                item.Remove();
            }
        }
        #endregion
    }
}
